﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Threading;
using Lync.Widget.Library.Extensions;
using Lync.Widget.Library.InstantMessaging;
using Lync.Widget.Library.Presence;
using Lync.Widget.Library.Repositories;
using Lync.Widget.Library.ServiceHelpers;
using Lync.Widget.Library.ServiceHelpers.ServiceHelpers;

using Microsoft.Rtc.Collaboration.Presence;

using log4net;
using log4net.Config;

namespace Lync.Widget.Library.Services
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class LyncService : ILyncService
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(LyncService));
        private readonly IActiveDirectoryRepository m_ActiveDirectoryRepository;
        private readonly IActiveDirectoryFieldMapper m_ActiveDirectoryFieldMapper;
        private IPresenceCache m_PresenceCache;

        #region Constructor

        public LyncService()
            : this(new ActiveDirectoryCachedRepository(), new ActiveDirectoryFieldMapper())
        {
            StartPresenceQueryService();
        }
        
        public LyncService(IActiveDirectoryRepository activeDirectoryRepository, IActiveDirectoryFieldMapper activeDirectoryFieldMapper)
        {
            m_ActiveDirectoryRepository = activeDirectoryRepository;
            m_ActiveDirectoryFieldMapper = activeDirectoryFieldMapper;
        }

        #endregion

        #region Public Methods

        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public List<PresenceEntry> GetPersonsAndGroups(string query, string sips, string groups)
        {
            var entries = new List<PresenceEntry>();
            if (!String.IsNullOrEmpty(query))
            {
                var persons = GetPersons(query);

                if (persons != null)
                {
                    entries.AddRange(persons.Select(p => new PresenceEntry { Person = p }));
                }    
            }

            if (!string.IsNullOrEmpty(sips))
            {
                var persons = GetPersonsBySip(sips);

                if (persons != null)
                {
                    entries.AddRange(persons.Select(p => new PresenceEntry { Person = p }));
                }  
            }

            if (!string.IsNullOrEmpty(groups))
            {
                var distributiongroups = GetGroups(groups);

                if (distributiongroups != null)
                {
                    entries.AddRange(distributiongroups.Select(g => new PresenceEntry { Group = g }));
                }
                foreach (var distributiongroup in distributiongroups)
                {
                    InitGroupChatSessionInternal(distributiongroup.Group, "0", "init", string.Empty, string.Empty, string.Empty,
                                         true);
                }
            }

            return entries;
        }


        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public List<SipEntry> GetPersons(string query)
        {
            List<SipEntry> sips = null;

            var isValidQueryString = m_ActiveDirectoryFieldMapper.ContainsValidQueryStringParams(query);

            if (isValidQueryString)
            {
                var users = m_ActiveDirectoryRepository.GetUsers(query);
                if (users != null)
                {
                    sips = users.Select(u => new SipEntry(u.SipAddress)).ToList();
                    Log.Info("Retrieved AD accounts for the query '" + query + "': " + string.Join(",", users.Select(u => u.Name)));
                }
            }

            return sips;
        }

        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public List<SipEntry> GetPersonsBySip(string sip)
        {
            var sips = sip.Split(',').ToList();

            var users = m_ActiveDirectoryRepository.GetUsers(sips);

            return users.Select(u => new SipEntry(u.SipAddress)).ToList();
        }

        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public bool ResetAll()
        {
            this.m_PresenceCache.ResetAll();
            return true;
        }


        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public List<GroupEntry> GetGroups(string groups)
        {
            var groupIds = groups.Split(',').ToList();

            var distributionGroups = m_ActiveDirectoryRepository.GetGroupsFromConfiguration()
                                                       .Where(g => groupIds.Any(id => id.ToLower() == g.Id.ToLower()))
                                                       .ToList();

            return distributionGroups.Select(g => new GroupEntry(g.Id)).ToList();
        }

        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public List<GroupEntry> GetAllGroups()
        {
            var distributionGroups = m_ActiveDirectoryRepository.GetGroupsFromConfiguration().ToList();
            return distributionGroups.Select(g => new GroupEntry(g.Id)).ToList();
        }

        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public GroupSubscription GetGroupPresence(string groupid)
        {
            var group = GetGroupsPresence(groupid, null).SingleOrDefault();

            if (group == null)
            {
                throw new Exception(String.Format("group with id'{0}' was not found", groupid));
            }
            return group;
        }

        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public List<Subscription> GetPresences(string sipUris, string groups, string availability, bool init)
        {
            var subscriptions = new List<Subscription>();

            if (!String.IsNullOrEmpty(sipUris))
            {
                Log.DebugFormat("Getting presences for SIPURIS '{0}', GROUPS: '{1}', AVAILABILITY: '{2}', INIT: {3}", sipUris, groups, availability, init);

                var presences = GetPresences(sipUris, availability, init);

                if (presences != null)
                {
                    subscriptions.AddRange(presences.Select(p => new Subscription { PresenceSubscription = p }));
                }
            }

            if (!String.IsNullOrEmpty(groups))
            {
                var presences = GetGroupsPresence(groups, availability);

                if (presences != null)
                {
                    subscriptions.AddRange(presences.Select(g => new Subscription { GroupSubscription = g }));
                }
            }

            return subscriptions;
        }


        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public List<GroupSubscription> GetGroupsPresence(string groups, string availability)
        {
            if (groups == null) throw new ArgumentNullException("groups");

            var groupIds = groups.Split(',').ToList();

            var distributionGroups = m_ActiveDirectoryRepository.GetGroupsFromConfiguration()
                                                       .Where(g => groupIds.Any(id => id.ToLower() == g.Id.ToLower()))
                                                       .ToList();

            var groupsFromAd = m_ActiveDirectoryRepository.GetGroups();

            var groupSubscriptions = m_PresenceCache.GetGroupPresence(distributionGroups).ToList();

            foreach (var groupSubscription in groupSubscriptions)
            {
                var adGroup = groupsFromAd.SingleOrDefault(g => g.Name == groupSubscription.GroupName);

                groupSubscription.OverrideSettings(adGroup, availability);
                
            }

            return groupSubscriptions;
        }

        

        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public ServiceCallResponse InitGroupChatSession(string groupId, string session, string username, string page, string title, string topic, bool isInInitialMode)
        {
            return InitGroupChatSessionInternal(groupId, session, username, page, title, topic, isInInitialMode);
        }

        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public ServiceCallResponse SendInstantGroupMessage(string groupId, string session, string message)
        {
            Log.Info(string.Format("Message sent from webuser for session {0}: {1}", session, message));
            try
            {
                UCInstantGroupMessageRouter.Instance.SendInstantMessageToLyncClient(session, message);
            }
            catch (Exception e)
            {
                Log.Error(string.Format("Unable to send message from webuser for session {0}.", session), e);
                return ServiceCallResponseFactory.CreateFatalErrorResponse(e.Message);
            }
            return ServiceCallResponseFactory.CreateSuccesfulResponse();
        }


        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public ServiceCallResponse InitChatSession(string session, string employeeSip, string username, string page, string title, string topic)
        {
            Log.Info(string.Format("Init chat requested from webUserName {0} for session {1} with employeeSip {2}, page {3}, topic {4}", username, session, employeeSip, page, topic));
            try
            {
                UCInstantMessageRouter.Instance.InitChatSession(session, employeeSip, username, page,title, topic);
            }
            catch (Exception e)
            {
                Log.Error(string.Format("Unable to init chat session {0}.", session), e);
                return ServiceCallResponseFactory.CreateFatalErrorResponse(e.Message);
            }
            return ServiceCallResponseFactory.CreateSuccesfulResponse();
        }


        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public ServiceCallResponse SendInstantMessage(string session, string message)
        {
            Log.Info(string.Format("Message sent from webuser for session {0}: {1}", session, message));
            try
            {
                UCInstantMessageRouter.Instance.SendInstantMessageToLyncClient(session, message);
            }
            catch (Exception e)
            {
                Log.Error(string.Format("Unable to send message from webuser for session {0}.", session), e);
                return ServiceCallResponseFactory.CreateFatalErrorResponse(e.Message);
            }
            return ServiceCallResponseFactory.CreateSuccesfulResponse();
        }

        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public ServiceCallResponse GetMessagesFromGroup(string groupId, string session)
        {
            List<GroupMessage> messages;
            Log.Info(string.Format("webuser requests instant messages for session {0}", session));
            try
            {
                var groupSubscription = GetGroupPresence(groupId);
                messages = UCInstantGroupMessageRouter.Instance.GetInstantMessagesFromLynClient(groupSubscription, session);
            }
            catch (Exception e)
            {
                Log.Error(string.Format("Unable to retrieve instant messages from queue for session {0}.", session), e);
                return ServiceCallResponseFactory.CreateFatalErrorResponse(e.Message);
            }
            return ServiceCallResponseFactory.CreateSuccesfulGroupResponse(messages);
        }

        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public ServiceCallResponse GetMessages(string session)
        {
            List<Message> messages;
            Log.Info(string.Format("webuser requests instant messages for session {0}", session));
            try
            {
                messages = UCInstantMessageRouter.Instance.GetInstantMessagesFromLynClient(session);
            }
            catch (Exception e)
            {
                Log.Error(string.Format("Unable to retrieve instant messages from queue for session {0}.", session), e);
                return ServiceCallResponseFactory.CreateFatalErrorResponse(e.Message);
            }
            return ServiceCallResponseFactory.CreateSuccesfulResponse(messages);
        }

        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public string LogStatusReport()
        {
            var statusReport = UCInstantMessageRouter.Instance.GetStatusReport();

            Log.Info("Status Report:");
            string answer = "Status Report: ";


            foreach (var property in typeof(StatusReport).GetProperties())
            {
                Log.Info("- " + property.Name + ": " + property.GetValue(statusReport, null));
                answer += "- " + property.Name + ": " + property.GetValue(statusReport, null);
            }

            return answer;
        }

        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json)]
        public string GetServiceStatus()
        {
            return "Service Status: Online";
        }

        #endregion

        #region Private Methods

        private ServiceCallResponse InitGroupChatSessionInternal(string groupId, string session, string username, string page, string title, string topic, bool isInInitialMode)
        {
            Log.Info(string.Format("Init chat requested from group {0} for webUserName {1} for session {2} with page {3}, title {4}  topic {5}", groupId, username, session, page, title, topic));
            try
            {
                var groupPresence = GetGroupPresence(groupId);
                UCInstantGroupMessageRouter.Instance.InitChatSession(groupPresence, session, username, page, title, topic, isInInitialMode);
            }
            catch (Exception e)
            {
                Log.Error(string.Format("Unable to init chat session {0}.", session), e);
                return ServiceCallResponseFactory.CreateFatalErrorResponse(e.Message);
            }

            return ServiceCallResponseFactory.CreateSuccesfulResponse();
        }

        private List<PresenceSubscription> GetPresences(string sipUris, string availability, bool init)
        {
            if (sipUris == null) throw new ArgumentNullException("sipUris");

            var subscriptions = new List<PresenceSubscription>();

            var sips = sipUris.Split(',').ToList();

            foreach (var sipUri in sips)
            {
                // TODO: probably do not send this to WS
                //if (sipUri == "{{= sip}}")
                //{
                //    continue;
                //}

                var adUser = m_ActiveDirectoryRepository.GetUser(sipUri);

                PresenceSubscription presence;
                if (init)
                {
                    presence = m_PresenceCache.GetCachedPresence(sipUri);
                    if (presence == null)
                    {
                        presence = m_PresenceCache.GetInitialPresence(sipUri);
                    }
                }
                else
                {
                    presence = m_PresenceCache.GetPresence(sipUri);    
                }
                
                if (presence != null)
                {
                    presence = presence.OverrideSettings(adUser, availability);
                    if (presence.ContactName.ToLowerInvariant() == Consts.Unknown.ToLowerInvariant() && adUser != null)
                    {
                        presence.ContactName = adUser.Name;
                    }

                    subscriptions.Add(presence);
                }
            }

            return subscriptions;
        }


        private void StartPresenceQueryService()
        {
            XmlConfigurator.Configure();

            Log.Info("LyncWidget service starting up");


            var applicationName = ConfigurationManager.AppSettings["ApplicationName"];
            var applicationId = ConfigurationManager.AppSettings["ApplicationId"];

            UcmaHelper.GetInstance().Start(applicationName, applicationId);

            if (UcmaHelper.GetInstance().ApplicationEndpoint == null)
            {
                throw new Exception(
                    String.Format(
                        "ApplicationEndpoint is not found for ApplicationName '{0}' and Application Id '{1}'",
                        applicationName, applicationId));
            }

            var provider = new UcPresenceProvider(UcmaHelper.GetInstance().ApplicationEndpoint);

            if (provider.Start())
            {
                m_PresenceCache = new UcPresenceCache(provider);
				Thread thread = new Thread(new ThreadStart(InitialGroupChat));
                thread.IsBackground = false;
				thread.Start();

                Log.Info("LyncWidget service started successfully");
            }
            else
            {
                Log.Info("LyncWidget service failed to start");
            }

            //Log.Info("LyncWidget service starting up");
            //var queryService = new PresenceQueryService.ServiceMock();
            //_presenceQueryServiceHost = new WebServiceHost(queryService);
            //_presenceQueryServiceHost.Open();
            //Log.Info("LyncWidget service started successfully");
        }

	    private void InitialGroupChat()
	    {
			Thread.Sleep(4000);
		    try
		    {
			    string intialGroupStr = ConfigurationManager.AppSettings["InitialGroupsAtStartServer"];
			    if (!string.IsNullOrEmpty(intialGroupStr))
			    {
				    foreach (string intialGroup in intialGroupStr.Split(new char[] {';', ','}))
				    {
                        InitGroupChatSessionInternal(intialGroup, "0", "init", string.Empty, string.Empty, string.Empty, true);
					    Log.Info("InitGroupChatSession for " + intialGroup);
				    }
			    }
				System.Threading.ThreadPool.QueueUserWorkItem(delegate
				{
					InitialGroupChat();
				}, null);
		    }
		    catch (Exception ex)
		    {
			    Log.Error("InitGroupChatSession", ex);
		    }
	    }

	    #endregion
    }
}